/*
 * Copyright 2020-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.demo;

import com.example.demo.filter.AuthenticationFilter;
import com.example.demo.filter.LoginFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Joe Grandja
 * @since 0.0.1
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RedisTemplate redisTemplate;

    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/webjars/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        InMemoryUserDetailsManager inMemoryUserDetailsManager = inMemoryUserDetailsManager();

        // 注册预认证过滤器
        http.addFilterBefore(new AuthenticationFilter(redisTemplate),
                LogoutFilter.class);
        http.addFilterBefore(new LoginFilter(inMemoryUserDetailsManager, redisTemplate),
                AuthenticationFilter.class);

        http.userDetailsService(inMemoryUserDetailsManager)
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests
                                .antMatchers("/login*").anonymous()
                                .anyRequest().authenticated())
                .logout().addLogoutHandler(new CustomLogoutHandler(inMemoryUserDetailsManager, redisTemplate));
        http.csrf().disable();
    }

    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user1")
                .password("password")
                .roles("USER")
                .authorities("read")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    /**
     * 自定以退出处理，用于处理自定的登录状态的注销
     * 实际会依次执行下面三个
     * org.springframework.security.provisioning.InMemoryUserDetailsManager
     * org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
     * org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler
     */
    record CustomLogoutHandler(
            InMemoryUserDetailsManager userDetailsService, RedisTemplate redisTemplate) implements LogoutHandler {
        private static final Log log = LogFactory.getLog(LogoutHandler.class);

        @Override
        public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
            String authorization = request.getHeader("Authorization");
            // 判断一下内容是否为空
            if (StringUtils.hasText(authorization)) {
                // 去掉token前缀(Bearer )，拿到真实token

                System.out.println(authorization);
                String authToken = authorization.substring(6).trim();

                if (SecurityContextHolder.getContext().getAuthentication() != null) {
                    String s2 = new String(Base64.getDecoder().decode(authToken.getBytes(StandardCharsets.UTF_8)));
                    if (log.isDebugEnabled()) {
                        log.debug("退出登录: " + s2.split(":")[0]);
                    }
                    redisTemplate.delete(LoginFilter.TOKEN_KEY + authToken);
                }
            }
        }
    }


    class AccessDecisionProcessor implements AccessDecisionVoter<FilterInvocation> {
        private static final Log log = LogFactory.getLog(AccessDecisionProcessor.class);

        @Override
        public boolean supports(ConfigAttribute attribute) {
            return true;
        }

        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }

        @Override
        public int vote(Authentication authentication, FilterInvocation object,
                        Collection<ConfigAttribute> attributes) {
            if (authentication == null) {
                return ACCESS_DENIED;
            }
            int result = ACCESS_ABSTAIN;

            // 拿到当前请求uri
            String requestUrl = object.getRequestUrl();
            String method = object.getRequest().getMethod();
            log.debug("进入自定义鉴权投票器，URI : " + method + " " + requestUrl);

            String key = requestUrl + ":" + method;
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authentication.getAuthorities().equals(key)) {
                    return ACCESS_GRANTED;
                }
            }
            return ACCESS_DENIED;
        }
    }

    Map<String, ResourceCache> map() {
        Map<String, ResourceCache> map = new HashMap<>();
        map.put("/index", new ResourceCache(new AntPathMatcher("/index"), Arrays.asList(HttpMethod.GET),
                Arrays.asList("ROLE"), Arrays.asList("read")));
        return map;
    }

    record ResourceCache(AntPathMatcher uri,
                         List<HttpMethod> method,
                         List<String> roles,
                         List<String> authorities) {

    }

    boolean match(Map<String, ResourceCache> map, Authentication authentication, FilterInvocation object) {
        String requestURI = object.getRequest().getRequestURI();
        String method = object.getRequest().getMethod();
        if (map.containsKey(requestURI)) {
            ResourceCache resourceCache = map.get(requestURI);

            return switch (resourceCache.roles) {
                case null -> switch (resourceCache.authorities) {
                    case null -> false;
                    case List<String> authorities -> authorities.stream().anyMatch(authentication.getAuthorities()::contains);
                };
                case List<String> roles -> switch (resourceCache.authorities) {
                    case null -> roles.stream().anyMatch(authentication.getAuthorities()::contains);
                    case List<String> authorities -> roles.stream().anyMatch(authentication.getAuthorities()::contains)
                            && authorities.stream().anyMatch(authentication.getAuthorities()::contains);
                };
            };

        } else {
            boolean m = true;
            for (ResourceCache resourceCache : map.values()) {

            }

        }
        return false;

    }

}